package com.example.dance.controller;

import com.example.dance.config.RateLimitConfig;
import com.google.common.util.concurrent.RateLimiter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.ClientAbortException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@RestController
@CrossOrigin
public class VideoStreamController {
    private static final Logger logger = LoggerFactory.getLogger(VideoStreamController.class);
    private static final String VIDEO_CONTENT_TYPE = "video/mp4";
    private static final String RANGE_PREFIX = "bytes=";
    private static final String UPLOAD_DIR = "upload";
    private static final Path VIDEO_ROOT = Paths.get(System.getProperty("user.dir"), UPLOAD_DIR).normalize();

    // 动态缓冲区大小，可通过JVM参数video.buffer.size调整
    private static final ThreadLocal<ByteBuffer> BUFFER_THREAD_LOCAL =
            ThreadLocal.withInitial(() -> ByteBuffer.allocateDirect(
                    Integer.getInteger("video.buffer.size", 100 * 1024 * 1024))); //

    // 视频文件通道缓存
    private static final VideoChannelCache CHANNEL_CACHE = new VideoChannelCache();

    private final RateLimiter rateLimiter;


    public VideoStreamController(RateLimiter rateLimiter) {
        this.rateLimiter = rateLimiter;
    }

    @GetMapping("/stream/{filename:.+}")
    public void streamVideo(
            @PathVariable String filename,
            HttpServletRequest request,
            HttpServletResponse response) {

        if (!isValidFilename(filename)) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            return;
        }

        Path resourcePath = VIDEO_ROOT.resolve(filename).normalize();
        if (!resourcePath.startsWith(VIDEO_ROOT)) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            return;
        }

        try {
            if (!Files.exists(resourcePath)) {
                response.sendError(HttpStatus.NOT_FOUND.value(), "Video not found");
                return;
            }

            configureResponseHeaders(response, resourcePath);
            processFileResource(resourcePath, request, response);

        } catch (ClientAbortException e) {
            logger.debug("Client aborted connection before video streaming started");
        } catch (Exception e) {
            if (!isClientDisconnectError(e)) {
                logger.error("Streaming error for file: {}", filename, e);
            }
        }
    }

    private void processFileResource(Path filePath,
                                     HttpServletRequest request,
                                     HttpServletResponse response) throws IOException {
        long startTime = System.nanoTime();
        try {
            // 从缓存获取文件通道
            FileChannel channel = CHANNEL_CACHE.get(filePath);
            long fileSize = channel.size();

            // 解析Range头
            Range range = parseRangeHeader(request.getHeader(HttpHeaders.RANGE), fileSize);

            if (range != null) {
                // 处理范围请求
                sendRangeContent(channel, range, response, fileSize);
            } else {
                // 处理完整文件请求
                sendFullContent(channel, response, fileSize);
            }
        } finally {
            // 记录性能数据
            monitorPerformance(filePath, startTime);
        }
    }

    private void sendRangeContent(FileChannel channel,
                                  Range range,
                                  HttpServletResponse response,
                                  long fileSize) throws IOException {
        long contentLength = range.end - range.start + 1;
        response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
        response.setHeader(HttpHeaders.CONTENT_RANGE,
                String.format("bytes %d-%d/%d", range.start, range.end, fileSize));
        response.setContentLengthLong(contentLength);

        try (OutputStream out = response.getOutputStream()) {
            MappedByteBuffer buffer = channel.map(
                    FileChannel.MapMode.READ_ONLY, range.start, contentLength);

            byte[] tempBuffer = new byte[8 * 1024 * 1024]; // KB缓冲区

            try {
                while (buffer.hasRemaining()) {
                    int remaining = Math.min(buffer.remaining(), tempBuffer.length);
                    buffer.get(tempBuffer, 0, remaining);
                    out.write(tempBuffer, 0, remaining);
                }
            } catch (ClientAbortException e) {
                // 客户端主动断开连接，无需处理
                logger.debug("Client aborted connection during video streaming");
            } catch (IOException e) {
                if (!isClientDisconnectError(e)) {
                    throw e;
                }
                logger.debug("Client disconnected during video streaming");
            } finally {
                clean(buffer);
            }
        }
    }

    private void sendFullContent(FileChannel channel,
                                 HttpServletResponse response,
                                 long fileSize) throws IOException {
        response.setContentLengthLong(fileSize);

        // 使用优化的传输方法
        transferWithRateLimit(channel, 0, fileSize,
                Channels.newChannel(response.getOutputStream()));
    }

    /**
     * 优化的数据传输方法，包含速率限制和连接重置保护
     */
    private void transferWithRateLimit(FileChannel src,
                                       long startPos,
                                       long totalBytes,
                                       WritableByteChannel dest) throws IOException {
        ByteBuffer buffer = BUFFER_THREAD_LOCAL.get();
        long transferred = 0;
        long position = startPos;

        try {
            while (transferred < totalBytes) {
                // 动态调整块大小，最大1MB
                int chunkSize = (int) Math.min(buffer.capacity(),
                        Math.min(1 * 1024 * 1024, totalBytes - transferred));

                // 非阻塞获取令牌，最多等待100ms
                if (!rateLimiter.tryAcquire(chunkSize, 100, TimeUnit.MILLISECONDS)) {
                    continue;
                }

                buffer.clear().limit(chunkSize);
                int bytesRead = src.read(buffer, position);
                if (bytesRead <= 0) break;

                buffer.flip();
                try {
                    while (buffer.hasRemaining()) {
                        dest.write(buffer);
                    }
                } catch (IOException e) {
                    if (isClientDisconnectError(e)) return;
                    throw e;
                }

                position += bytesRead;
                transferred += bytesRead;
            }
        } finally {
            buffer.clear(); // 确保缓冲区被清理
        }
    }

    private void monitorPerformance(Path filePath, long startTime) {
        long duration = System.nanoTime() - startTime;
        if (duration > 100_000_000) { // 超过100ms记录为慢请求
            logger.info("Slow video request for {} took {}ms",
                    filePath.getFileName(), duration / 1_000_000);
        }
    }

    private void configureResponseHeaders(HttpServletResponse response, Path filePath) {
        response.setContentType(VIDEO_CONTENT_TYPE);
        response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");

        // 优化缓存控制
        response.setHeader(HttpHeaders.CACHE_CONTROL, "public, max-age=86400");

        // 添加视频信息头
        try {
            response.setHeader("X-Video-Length", String.valueOf(Files.size(filePath)));
        } catch (IOException e) {
            logger.debug("Failed to get file size", e);
        }
    }

    private Range parseRangeHeader(String rangeHeader, long fileSize) {
        if (rangeHeader == null || !rangeHeader.startsWith(RANGE_PREFIX)) {
            return null;
        }

        try {
            String rangeValue = rangeHeader.substring(RANGE_PREFIX.length()).trim();
            int dashIndex = rangeValue.indexOf('-');

            long start = parseLong(rangeValue.substring(0, dashIndex));
            long end = dashIndex < rangeValue.length() - 1
                    ? parseLong(rangeValue.substring(dashIndex + 1))
                    : fileSize - 1;

            // 确保范围有效
            start = Math.max(0, start);
            end = Math.min(fileSize - 1, end);
            return start <= end ? new Range(start, end) : null;
        } catch (Exception e) {
            logger.debug("Invalid range header: {}", rangeHeader);
            return null;
        }
    }

    private boolean isValidFilename(String filename) {
        return filename != null && filename.endsWith(".mp4")
                && filename.matches("[\\w-]+\\.mp4");
    }

    private boolean isClientDisconnectError(Exception e) {
        if (e == null) return false;

        // 检查已知的客户端断开连接异常类型
        if (e instanceof ClientAbortException) {
            return true;
        }

        String message = e.getMessage();
        if (message == null) return false;

        message = message.toLowerCase();
        return message.contains("connection reset by peer") ||
                message.contains("broken pipe") ||
                message.contains("connection aborted") ||
                message.contains("established connection was aborted") ||
                message.contains("你的主机中的软件中止了一个已建立的连接");
    }

    private static long parseLong(String s) {
        return Long.parseLong(s.trim());
    }

    /**
     * 清理MappedByteBuffer
     */
    private static void clean(MappedByteBuffer buffer) {
        if (buffer != null && buffer.isDirect()) {
            try {
                Method cleanerMethod = buffer.getClass().getMethod("cleaner");
                cleanerMethod.setAccessible(true);
                Object cleaner = cleanerMethod.invoke(buffer);
                if (cleaner != null) {
                    Method cleanMethod = cleaner.getClass().getMethod("clean");
                    cleanMethod.invoke(cleaner);
                }
            } catch (Exception e) {
                // 忽略清理失败
            }
        }
    }

    /**
     * 视频文件通道缓存
     */
    private static class VideoChannelCache {
        private final ConcurrentMap<Path, FileChannel> cache = new ConcurrentHashMap<>();
        private final ScheduledExecutorService cleanupExecutor =
                Executors.newSingleThreadScheduledExecutor();

        public VideoChannelCache() {
            // 每10分钟清理一次缓存
            cleanupExecutor.scheduleAtFixedRate(() -> {
                cache.entrySet().removeIf(entry -> {
                    try {
                        return !entry.getValue().isOpen();
                    } catch (Exception e) {
                        return true;
                    }
                });
            }, 10, 10, TimeUnit.MINUTES);
        }

        public FileChannel get(Path path) throws IOException {
            return cache.computeIfAbsent(path, p -> {
                try {
                    return FileChannel.open(p, StandardOpenOption.READ);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
        }
    }

    /**
     * 范围请求数据类
     */
    private static class Range {
        final long start;
        final long end;

        Range(long start, long end) {
            this.start = start;
            this.end = end;
        }
    }
}